#include <arch/intr.h>
#include <arch/lapic.h>

#include <mm/paging.h>
#include <mm/page_allocator.h>

#include <task/sched.h>

#include <drv/serial.h>

typedef struct __packed {
	uint16_t offset0;
	uint16_t selector;
	uint8_t ist;
	uint8_t attributes;
	uint16_t offset1;
	uint32_t offset2;
	uint32_t reserved;
} intr_gate_entry_t;

static intr_gate_entry_t *idt = NULL;
static const char *irq_names[] = {
		"Divide-by-zero",
		"Debug",
		"Non-maskable interrupt",
		"Breakpoint",
		"Overflow",
		"Bound range exceeded",
		"Invalid opcode",
		"No coprocessor",
		"Double fault",
		"Coprocessor segment overrun",
		"Invalid TSS",
		"Segment not present",
		"Stack-segment fault",
		"General protection fault",
		"Page fault",
		"Unknown interrupt",
		"FPU floating-point error",
		"Alignment check",
		"Machine check",
		"SIMD floating-point exception",
};

void intr_set_idt() {
}

static void intr_set_gate(intr_gate_entry_t *entry, uintptr_t entry_point, uint16_t selector, uint8_t ist, uint8_t type) {
	entry->offset0 = (entry_point & 0xffff);
	entry->selector = selector;
	entry->ist = (ist & 7);
	entry->attributes = type;
	entry->offset1 = ((entry_point >> 16) & 0xffff);
	entry->offset2 = ((entry_point >> 32) & 0xffffffff);
	entry->reserved = 0;
}

intr_regs_frame_t *intr_handler(intr_regs_frame_t *frame) {
	if(frame->irq_index == 0x20) {
		frame = sched_tick(frame);

		// serial_printf("frame=%p\n", frame);

		lapic_write32(0xb0, 0);

		return frame;
	}

	const char *irq_name = NULL;
	if(frame->irq_index < sizeof(irq_names) / sizeof(irq_names[0])) {
		irq_name = irq_names[frame->irq_index];
	} else {
		irq_name = "Other";
	}

	serial_printf("intr_handler: intr triggered!\n");
	serial_printf("  v=%02x(%s) e=%04x cpl=%d next_ip=%04x:%016llx rflags=%08x apic_id=%08x\n", frame->irq_index, irq_name, frame->err_code, frame->cs & 3, frame->cs, frame->rip, frame->rflags, lapic_get_self_id());

	if(frame->irq_index < 0x20) {
		serial_printf("register dump:\n");
		serial_printf("rax=%016llx rbx=%016llx rcx=%016llx rdx=%016llx\n", frame->regs.rax, frame->regs.rbx, frame->regs.rcx, frame->regs.rdx);
		serial_printf("rsi=%016llx rdi=%016llx rsp=%016llx rbp=%016llx\n", frame->regs.rsi, frame->regs.rdi, frame->rsp, frame->regs.rbp);
		serial_printf("r8 =%016llx r9 =%016llx r10=%016llx r11=%016llx\n", frame->regs.r8, frame->regs.r9, frame->regs.r10, frame->regs.r11);
		serial_printf("r12=%016llx r13=%016llx r14=%016llx r15=%016llx\n", frame->regs.r12, frame->regs.r13, frame->regs.r14, frame->regs.r15);
	}

	if(frame->irq_index == 0x0e) {
		uintptr_t cr2 = 0;
		__asm__ volatile("mov %%cr2, %0" : "=r"(cr2));
		serial_printf("cr2=%016llx\n", cr2);
	}

	while(1);

	return frame;
}

void intr_init() {
	if(idt) {
		return;
	}

	idt = (intr_gate_entry_t *)mm_pallocator_valloc4k(1, PAGE_P | PAGE_RW);

	uintptr_t fn = 0;

#define INTR_GATE_SET(n, is_exception)                               \
	__asm__ volatile("leaq intr_entry_" #n "(%%rip), %0;" : "=r"(fn)); \
	intr_set_gate(&idt[0x##n], fn, 1 << 3, 0, (is_exception) ? 0x8f : 0x8e)

	INTR_GATE_SET(00, true);
	INTR_GATE_SET(01, true);
	INTR_GATE_SET(02, true);
	INTR_GATE_SET(03, true);
	INTR_GATE_SET(04, true);
	INTR_GATE_SET(05, true);
	INTR_GATE_SET(06, true);
	INTR_GATE_SET(07, true);
	INTR_GATE_SET(08, true);
	INTR_GATE_SET(0a, true);
	INTR_GATE_SET(0b, true);
	INTR_GATE_SET(0c, true);
	INTR_GATE_SET(0d, true);
	INTR_GATE_SET(0e, true);
	INTR_GATE_SET(10, true);
	INTR_GATE_SET(11, true);
	INTR_GATE_SET(12, true);
	INTR_GATE_SET(13, true);
	INTR_GATE_SET(14, true);
	INTR_GATE_SET(15, true);
	INTR_GATE_SET(16, true);
	INTR_GATE_SET(17, true);
	INTR_GATE_SET(18, true);
	INTR_GATE_SET(19, true);
	INTR_GATE_SET(1a, true);
	INTR_GATE_SET(1b, true);
	INTR_GATE_SET(1c, true);
	INTR_GATE_SET(1d, true);
	INTR_GATE_SET(1e, true);
	INTR_GATE_SET(1f, true);
	INTR_GATE_SET(20, false);
	INTR_GATE_SET(21, false);
	INTR_GATE_SET(22, false);
	INTR_GATE_SET(23, false);
	INTR_GATE_SET(24, false);
	INTR_GATE_SET(25, false);
	INTR_GATE_SET(26, false);
	INTR_GATE_SET(27, false);
	INTR_GATE_SET(28, false);
	INTR_GATE_SET(29, false);
	INTR_GATE_SET(2a, false);
	INTR_GATE_SET(2b, false);
	INTR_GATE_SET(2c, false);
	INTR_GATE_SET(2d, false);
	INTR_GATE_SET(2e, false);
	INTR_GATE_SET(2f, false);
	INTR_GATE_SET(30, false);
	INTR_GATE_SET(31, false);
	INTR_GATE_SET(32, false);
	INTR_GATE_SET(33, false);
	INTR_GATE_SET(34, false);
	INTR_GATE_SET(35, false);
	INTR_GATE_SET(36, false);
	INTR_GATE_SET(37, false);
	INTR_GATE_SET(38, false);
	INTR_GATE_SET(39, false);
	INTR_GATE_SET(3a, false);
	INTR_GATE_SET(3b, false);
	INTR_GATE_SET(3c, false);
	INTR_GATE_SET(3d, false);
	INTR_GATE_SET(3e, false);
	INTR_GATE_SET(3f, false);
	INTR_GATE_SET(40, false);
	INTR_GATE_SET(41, false);
	INTR_GATE_SET(42, false);
	INTR_GATE_SET(43, false);
	INTR_GATE_SET(44, false);
	INTR_GATE_SET(45, false);
	INTR_GATE_SET(46, false);
	INTR_GATE_SET(47, false);
	INTR_GATE_SET(48, false);
	INTR_GATE_SET(49, false);
	INTR_GATE_SET(4a, false);
	INTR_GATE_SET(4b, false);
	INTR_GATE_SET(4c, false);
	INTR_GATE_SET(4d, false);
	INTR_GATE_SET(4e, false);
	INTR_GATE_SET(4f, false);
	INTR_GATE_SET(50, false);
	INTR_GATE_SET(51, false);
	INTR_GATE_SET(52, false);
	INTR_GATE_SET(53, false);
	INTR_GATE_SET(54, false);
	INTR_GATE_SET(55, false);
	INTR_GATE_SET(56, false);
	INTR_GATE_SET(57, false);
	INTR_GATE_SET(58, false);
	INTR_GATE_SET(59, false);
	INTR_GATE_SET(5a, false);
	INTR_GATE_SET(5b, false);
	INTR_GATE_SET(5c, false);
	INTR_GATE_SET(5d, false);
	INTR_GATE_SET(5e, false);
	INTR_GATE_SET(5f, false);
	INTR_GATE_SET(60, false);
	INTR_GATE_SET(61, false);
	INTR_GATE_SET(62, false);
	INTR_GATE_SET(63, false);
	INTR_GATE_SET(64, false);
	INTR_GATE_SET(65, false);
	INTR_GATE_SET(66, false);
	INTR_GATE_SET(67, false);
	INTR_GATE_SET(68, false);
	INTR_GATE_SET(69, false);
	INTR_GATE_SET(6a, false);
	INTR_GATE_SET(6b, false);
	INTR_GATE_SET(6c, false);
	INTR_GATE_SET(6d, false);
	INTR_GATE_SET(6e, false);
	INTR_GATE_SET(6f, false);
	INTR_GATE_SET(70, false);
	INTR_GATE_SET(71, false);
	INTR_GATE_SET(72, false);
	INTR_GATE_SET(73, false);
	INTR_GATE_SET(74, false);
	INTR_GATE_SET(75, false);
	INTR_GATE_SET(76, false);
	INTR_GATE_SET(77, false);
	INTR_GATE_SET(78, false);
	INTR_GATE_SET(79, false);
	INTR_GATE_SET(7a, false);
	INTR_GATE_SET(7b, false);
	INTR_GATE_SET(7c, false);
	INTR_GATE_SET(7d, false);
	INTR_GATE_SET(7e, false);
	INTR_GATE_SET(7f, false);
	INTR_GATE_SET(80, false);
	INTR_GATE_SET(81, false);
	INTR_GATE_SET(82, false);
	INTR_GATE_SET(83, false);
	INTR_GATE_SET(84, false);
	INTR_GATE_SET(85, false);
	INTR_GATE_SET(86, false);
	INTR_GATE_SET(87, false);
	INTR_GATE_SET(88, false);
	INTR_GATE_SET(89, false);
	INTR_GATE_SET(8a, false);
	INTR_GATE_SET(8b, false);
	INTR_GATE_SET(8c, false);
	INTR_GATE_SET(8d, false);
	INTR_GATE_SET(8e, false);
	INTR_GATE_SET(8f, false);
	INTR_GATE_SET(90, false);
	INTR_GATE_SET(91, false);
	INTR_GATE_SET(92, false);
	INTR_GATE_SET(93, false);
	INTR_GATE_SET(94, false);
	INTR_GATE_SET(95, false);
	INTR_GATE_SET(96, false);
	INTR_GATE_SET(97, false);
	INTR_GATE_SET(98, false);
	INTR_GATE_SET(99, false);
	INTR_GATE_SET(9a, false);
	INTR_GATE_SET(9b, false);
	INTR_GATE_SET(9c, false);
	INTR_GATE_SET(9d, false);
	INTR_GATE_SET(9e, false);
	INTR_GATE_SET(9f, false);
	INTR_GATE_SET(a0, false);
	INTR_GATE_SET(a1, false);
	INTR_GATE_SET(a2, false);
	INTR_GATE_SET(a3, false);
	INTR_GATE_SET(a4, false);
	INTR_GATE_SET(a5, false);
	INTR_GATE_SET(a6, false);
	INTR_GATE_SET(a7, false);
	INTR_GATE_SET(a8, false);
	INTR_GATE_SET(a9, false);
	INTR_GATE_SET(aa, false);
	INTR_GATE_SET(ab, false);
	INTR_GATE_SET(ac, false);
	INTR_GATE_SET(ad, false);
	INTR_GATE_SET(ae, false);
	INTR_GATE_SET(af, false);
	INTR_GATE_SET(b0, false);
	INTR_GATE_SET(b1, false);
	INTR_GATE_SET(b2, false);
	INTR_GATE_SET(b3, false);
	INTR_GATE_SET(b4, false);
	INTR_GATE_SET(b5, false);
	INTR_GATE_SET(b6, false);
	INTR_GATE_SET(b7, false);
	INTR_GATE_SET(b8, false);
	INTR_GATE_SET(b9, false);
	INTR_GATE_SET(ba, false);
	INTR_GATE_SET(bb, false);
	INTR_GATE_SET(bc, false);
	INTR_GATE_SET(bd, false);
	INTR_GATE_SET(be, false);
	INTR_GATE_SET(bf, false);
	INTR_GATE_SET(c0, false);
	INTR_GATE_SET(c1, false);
	INTR_GATE_SET(c2, false);
	INTR_GATE_SET(c3, false);
	INTR_GATE_SET(c4, false);
	INTR_GATE_SET(c5, false);
	INTR_GATE_SET(c6, false);
	INTR_GATE_SET(c7, false);
	INTR_GATE_SET(c8, false);
	INTR_GATE_SET(c9, false);
	INTR_GATE_SET(ca, false);
	INTR_GATE_SET(cb, false);
	INTR_GATE_SET(cc, false);
	INTR_GATE_SET(cd, false);
	INTR_GATE_SET(ce, false);
	INTR_GATE_SET(cf, false);
	INTR_GATE_SET(d0, false);
	INTR_GATE_SET(d1, false);
	INTR_GATE_SET(d2, false);
	INTR_GATE_SET(d3, false);
	INTR_GATE_SET(d4, false);
	INTR_GATE_SET(d5, false);
	INTR_GATE_SET(d6, false);
	INTR_GATE_SET(d7, false);
	INTR_GATE_SET(d8, false);
	INTR_GATE_SET(d9, false);
	INTR_GATE_SET(da, false);
	INTR_GATE_SET(db, false);
	INTR_GATE_SET(dc, false);
	INTR_GATE_SET(dd, false);
	INTR_GATE_SET(de, false);
	INTR_GATE_SET(df, false);
	INTR_GATE_SET(e0, false);
	INTR_GATE_SET(e1, false);
	INTR_GATE_SET(e2, false);
	INTR_GATE_SET(e3, false);
	INTR_GATE_SET(e4, false);
	INTR_GATE_SET(e5, false);
	INTR_GATE_SET(e6, false);
	INTR_GATE_SET(e7, false);
	INTR_GATE_SET(e8, false);
	INTR_GATE_SET(e9, false);
	INTR_GATE_SET(ea, false);
	INTR_GATE_SET(eb, false);
	INTR_GATE_SET(ec, false);
	INTR_GATE_SET(ed, false);
	INTR_GATE_SET(ee, false);
	INTR_GATE_SET(ef, false);
	INTR_GATE_SET(f0, false);
	INTR_GATE_SET(f1, false);
	INTR_GATE_SET(f2, false);
	INTR_GATE_SET(f3, false);
	INTR_GATE_SET(f4, false);
	INTR_GATE_SET(f5, false);
	INTR_GATE_SET(f6, false);
	INTR_GATE_SET(f7, false);
	INTR_GATE_SET(f8, false);
	INTR_GATE_SET(f9, false);
	INTR_GATE_SET(fa, false);
	INTR_GATE_SET(fb, false);
	INTR_GATE_SET(fc, false);
	INTR_GATE_SET(fd, false);
	INTR_GATE_SET(fe, false);
	INTR_GATE_SET(ff, false);

#undef INTR_GATE_SET
}

void intr_load_idt() {
	if(!idt) {
		__asm__ volatile("hlt");
	}

	struct __packed {
		uint16_t limit;
		uint64_t base;
	} idtr = {
			.limit = 0xfff,
			.base = (uint64_t)idt,
	};

	__asm__ volatile("lidt %0" : : "m"(idtr));
}